package io.wongxd.solution.dsl.net

import android.util.Log
import kotlinx.coroutines.*
import org.json.JSONObject
import java.io.*
import java.math.BigDecimal
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLEncoder
import java.util.*


/**
 * HttpUrlConnection 的 dsl
 *
 * Created by wongxd on 2018/11/20.
 * https://github.com/wongxd
 * wxd1@live.com
 */
object BaseHttpUrlConnectionHelper {

    var showLog = true

    private const val line = "#----------------------------------#"

    val netCoroutineScope by lazy { CoroutineScope(Job() + Dispatchers.IO) }

    fun netLog(preTag: String = "", info: CharSequence) {
        if (!showLog) return

        Log.d("netDsl", line)
        Log.d("netDsl", "$preTag:   $info")
        Log.d("netDsl", line)
    }

    private var doToast: ((String) -> Unit)? = null

    fun configToast(doToast: (String) -> Unit) {
        this.doToast = doToast
    }

    /**
     * 通用的http请求封装，不包含任何个性化参数
     */
    fun baseHttpUrlConnection(
        wrap: RequestWrapper,
        isDownload: Boolean = false,
        scope: CoroutineScope = netCoroutineScope
    ) {
        scope.launch(Dispatchers.IO) {
            if (isDownload)
                downloadFile(wrap)
            else
                onExecute(wrap)
        }
    }

    suspend fun doConnection(wrap: RequestWrapper, isDownload: Boolean = false) {
        withContext(Dispatchers.IO) {
            if (isDownload)
                downloadFile(wrap)
            else
                onExecute(wrap)
        }
    }

    private const val twoHyphens = "--"
    private const val boundary = "*****"
    private const val end = "\r\n"
    private const val separator = "$twoHyphens$boundary$end"

    private fun encode(value: String): String {
        return URLEncoder.encode(value, "UTF-8")
    }

    private fun packOneParams(
        dataOutputStream: DataOutputStream,
        paramName: String,
        paramValue: String
    ) {
        kotlin.runCatching {
            dataOutputStream.writeBytes(separator)
            dataOutputStream.writeBytes("""Content-Disposition: form-data; name="${encode(paramName)}" """)
            dataOutputStream.writeBytes(end)
            dataOutputStream.writeBytes("Content-Type:text/plain")
            dataOutputStream.writeBytes(end)
            dataOutputStream.writeBytes(end)
            dataOutputStream.writeBytes(encode(paramValue) + end)
            dataOutputStream.writeBytes(separator)
        }
    }

    /**
     * @param totalLength  所有文件的总长度
     * @param index 当前文件在所有上传文件中的位置
     */
    private suspend fun packOneFile(
        isImg: Boolean,
        file: MutableMap.MutableEntry<String, File>,
        dataOutputStream: DataOutputStream,
        //progress  totalLength index
        onProgressUpdate: (Int, Long, Int) -> Unit,
        totalLength: Long,
        index: Int
    ) {

        kotlin.runCatching {
            val filename = file.value.name
            val extension = file.value.extension
            val fileLength = file.value.length()

            dataOutputStream.writeBytes(twoHyphens + boundary + end)
            // 设定传送的内容类型是可序列化的java对象
            // (如果不设此项,在传送序列化对象时,当WEB服务默认的不是这种类型时可能抛java.io.EOFException)
            dataOutputStream.writeBytes("""Content-Disposition: form-data; name="${file.key}"; filename="$filename" $end""")
            if (isImg) {
                dataOutputStream.writeBytes("Content-Type: image/$extension")
            } else {
                dataOutputStream.writeBytes("Content-Type: file/$extension")
            }

            dataOutputStream.writeBytes(end)
            dataOutputStream.writeBytes("Content-Length:${fileLength}")
            dataOutputStream.writeBytes(end)
            dataOutputStream.writeBytes(end)

            val dataInputStream = DataInputStream(FileInputStream(file.value))
            var count = 0

            // 计算上传进度
            var progress: Long = 0L

            val buffer = ByteArray(2 * 1024)

            while (dataInputStream.read(buffer).also { count = it } != -1) {
                dataOutputStream.write(buffer, 0, count)

                progress += count.toLong()
                //换算进度

                val d =
                    BigDecimal(progress / fileLength.toDouble()).setScale(
                        2,
                        BigDecimal.ROUND_HALF_UP
                    ).toDouble()

                val d1 = d * 100

                withContext(Dispatchers.Main) {
                    //d1.toInt() 的值为1-100
                    onProgressUpdate(d1.toInt(), totalLength, index)
                }

            }
            dataOutputStream.writeBytes(end)
            /* close streams */
            dataInputStream.close()
        }
    }

    /**
     * 从response 的 inputStream 中 获取响应内容
     */
    private fun is2String(inputStream: InputStream): String? {
        return try {
            val reader = BufferedReader(InputStreamReader(inputStream, "utf-8"))
            val ssb = StringBuilder()
            var tempLine = ""
            while (reader.readLine()?.also { tempLine = it } != null) {
                ssb.append(tempLine + "\n")
            }
            inputStream.close()
            ssb.toString()
        } catch (e: Exception) {
            null
        }
    }

    /**
     * 处理响应
     */
    private suspend fun dealResponse(
        responseCode: Int,
        responseMsg: String,
        responseString: String,
        wrap: RequestWrapper
    ) {

        if (responseCode >= 300 || responseCode != HttpURLConnection.HTTP_OK) {
            val tips =
                "HTTP Request is not success, Response code is $responseCode,msg is $responseMsg"
            netLog("dealResponse-tips", tips)
            wrap.callFailed(responseCode, responseMsg)
            return
        }

        if (responseCode == HttpURLConnection.HTTP_OK) {
            withContext(Dispatchers.Main) {

                netLog("dealResponse", " ${wrap.url} \n $responseString")

                if (responseString.isNotBlank()) {
                    wrap._finish.invoke()
                    wrap._originResponse.invoke(responseString)

                    if (wrap.decodeRespAsJsonAndJudge) {
                        try {
                            val json = try {
                                JSONObject(responseString)
                            } catch (e: Exception) {
                                e.printStackTrace()
                                JSONObject("""{"${wrap.RESPONSE_CODE_NAME}":-1,"${wrap.RESPONSE_MSG_NAME}":"返回数据有误","data":""}""")
                            }
                            val errCode = json.optInt(wrap.RESPONSE_CODE_NAME)
                            val errMsg = json.optString(wrap.RESPONSE_MSG_NAME)
                            val dataPart = json.optString(wrap.RESPONSE_DATA_NAME)

                            wrap._httpSuccess.invoke(responseString, errCode, errMsg, dataPart)

                            if (errCode != wrap.SUCUUESSED_CODE) {
                                if (wrap.IS_SHOW_MSG) {
                                    doToast?.invoke(errMsg)
                                }
                                if (errCode == wrap.TOKEN_LOST_CODE) {
                                    //token失效
                                    wrap._tokenLost.invoke(errMsg)
                                } else {
                                    wrap.callFailed(code = errCode, msg = errMsg)
                                }
                            } else {
                                wrap._success.invoke(responseString)
                                wrap._successJustDataPart(if (dataPart == "null") "" else dataPart)
                                wrap._successWithDataPart(responseString, dataPart)
                                wrap._successWithMsg.invoke(responseString, errMsg)
                            }
                        } catch (e: Exception) {
                            e.printStackTrace()
                        }
                    }

                } else {
                    wrap.callFailed(responseCode)
                }

            }
        }
    }

    private suspend fun onExecute(wrap: RequestWrapper) {
        kotlin.runCatching {
            val isPost = wrap.method.des.uppercase(Locale.getDefault()).contains("POST")

            val isMultipart = wrap.imgs.isNotEmpty() || wrap.files.isNotEmpty()

            val isPostJson = wrap.jsonParam.isNotBlank()

            if (isMultipart || isPostJson) {
                if (!isPost) {
                    throw IllegalArgumentException("必须使用post方法")
                }
            }

            val encodeParams = mutableMapOf<String, String>()
            wrap.params.keys.forEach { k ->
                encodeParams[URLEncoder.encode(k.trim(), Charsets.UTF_8.name())] =
                    URLEncoder.encode(wrap.params.getValue(k).trim(), Charsets.UTF_8.name())
            }
            wrap.params.clear()
            wrap.params.putAll(encodeParams)

            try {

                if (!isPost) {
                    //get请求带参数
                    val sbGet = StringBuffer()
                    sbGet.append("?")
                    wrap.params.forEach { param ->
                        sbGet.append(param.key + "=" + param.value + "&")
                    }

                    val getParams = sbGet.toString()

                    wrap.url = wrap.url + getParams.substring(0, getParams.lastIndex)
                }


                // 统一资源
                val url: URL = URL(wrap.url)
                // 连接类的父类，抽象类 as  http的连接类
                val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
                // 设定请求的方法，默认是GET
                connection.requestMethod = if (isPost) "POST" else "GET"
                // 设置字符编码连接参数
                connection.addRequestProperty("Connection", "Keep-Alive")
                // 设置字符编码
                connection.addRequestProperty("Charset", "UTF-8")
                // 设置请求内容类型

                when {
                    isPostJson -> {
                        connection.addRequestProperty(
                            "Content-Type",
                            "application/json;charset=utf-8"
                        )
                    }
                    isMultipart -> {
                        connection.addRequestProperty(
                            "Content-Type",
                            "multipart/form-data;boundary=$boundary"
                        )
                    }
                    else -> {
                        connection.setRequestProperty(
                            "Content-Type",
                            "application/x-www-form-urlencoded"
                        )
                    }
                }


                // 设置是否向httpUrlConnection输出，因为这个是post请求，参数要放在
                // http正文内，因此需要设为true, 默认情况下是false;
                connection.doOutput = isPost
                //设置是否从httpUrlConnection读入，默认情况下是true;
                connection.doInput = true
                // Post 请求不能使用缓存
                connection.useCaches = false
                connection.connectTimeout = wrap.timeout.toInt()
                connection.readTimeout = wrap.readTimeout.toInt()

                wrap.headers.forEach { header ->
                    connection.addRequestProperty(header.key, header.value)
                }


                if (isPost) {

                    //会自动开启连接 不用 再调用   connection.connect()
                    val outputStream = connection.outputStream

                    // 设置DataOutputStream
                    val dataOutputStream: DataOutputStream = DataOutputStream(outputStream)

                    when {
                        isMultipart -> {
                            //文件 参数 一起 post

                            val sb = StringBuilder()
                            wrap.params.keys.forEach { key ->
                                packOneParams(dataOutputStream, key, wrap.params[key] ?: "")
                                sb.append(key + "--" + wrap.params[key])
                                sb.append(end)
                            }
                            netLog("request ${wrap.url}", sb.toString())


                            var totalFileLength = 0L
                            wrap.imgs.forEach { img ->
                                totalFileLength += img.value.length()
                            }

                            wrap.files.forEach { file ->
                                totalFileLength += file.value.length()
                            }

                            var index = 0
                            //imgs
                            for (img in wrap.imgs) {
                                packOneFile(
                                    true,
                                    img,
                                    dataOutputStream,
                                    wrap._uploadFile,
                                    totalFileLength,
                                    index
                                )
                                index++
                            }

                            //files
                            for (file in wrap.files) {
                                packOneFile(
                                    false,
                                    file,
                                    dataOutputStream,
                                    wrap._uploadFile,
                                    totalFileLength,
                                    index
                                )
                                index++
                            }

                            dataOutputStream.writeBytes("$twoHyphens$boundary$twoHyphens$end")

                            /* close streams */
                            dataOutputStream.flush()
                            dataOutputStream.close()

                        }
                        isPostJson -> {
                            //post json

                            netLog("request ${wrap.url}", wrap.jsonParam)

                            dataOutputStream.write(wrap.jsonParam.toByteArray())
                            dataOutputStream.flush()
                            dataOutputStream.close()

                        }
                        else -> {
                            //普通post

                            val sbPost = StringBuffer()
                            wrap.params.onEachIndexed { index, entry ->
                                sbPost.append("${if (index == 0) "" else "&"}${entry.key}=${entry.value}")
                            }
                            val realParams = sbPost.toString()

                            netLog("request ${wrap.url}", wrap.params.toString())
                            netLog("request ${wrap.url}", realParams)

                            dataOutputStream.write(realParams.toByteArray())
                            dataOutputStream.flush()
                            dataOutputStream.close()
                        }
                    }


                    outputStream.close()
                } else {
                    //开启连接
                    connection.connect()
                }


                //////////处理响应开始

                if (connection.responseCode != 200) {
                    wrap.callFailed(connection.responseCode, connection.responseMessage)
                    return@runCatching
                }

                val inputStream = connection.inputStream

                val responseString = is2String(inputStream) ?: ""

                dealResponse(
                    connection.responseCode,
                    connection.responseMessage,
                    responseString,
                    wrap
                )

                inputStream?.close()
                connection.disconnect()

            } catch (e: Exception) {
                e.printStackTrace()
                wrap.callFailed()
            }
        }
    }

    private suspend fun RequestWrapper.callFailed(code: Int = -1, msg: String = "网络没有响应") {
        val wrap = this
        withContext(Dispatchers.Main) {
            wrap._finish.invoke()
            if (wrap.IS_SHOW_MSG) {
                doToast?.invoke(msg)
            }
            wrap._fail.invoke(code, msg)
        }
    }

    private suspend fun downloadFile(wrap: RequestWrapper) {
        kotlin.runCatching {
            if (wrap.downloadFileName.isBlank()) {
                throw  java.lang.IllegalArgumentException("请设置下载文件的保存名字")
            }

            val url = URL(wrap.url)
            val connection = url.openConnection() as HttpURLConnection
            connection.requestMethod = "GET"
            connection.connect()

            try {
                val inputStream = connection.inputStream
                val responseCode = connection.responseCode
                val responseMsg = connection.responseMessage

                if (responseCode == HttpURLConnection.HTTP_OK) {
                    val buf = ByteArray(1024 * 8)
                    var len = 0
                    var fos: FileOutputStream? = null
                    val total = connection.contentLength
                    var sum: Long = 0
                    val dir = File(wrap.downloadPath)
                    if (!dir.exists()) {
                        dir.mkdirs()
                    }
                    val file = File(dir, wrap.downloadFileName) //根据目录和文件名得到file对象
                    fos = FileOutputStream(file)

                    while ((inputStream.read(buf)).also { len = it } != -1) {
                        sum += len.toLong()
                        fos.write(buf, 0, len)
                        withContext(Dispatchers.Main) {
                            wrap._downloadFile(
                                (sum * 1.0f / total * 100).toInt(),
                                total.toLong(),
                                null
                            )
                        }
                    }

                    fos.flush()
                    fos.close()

                    withContext(Dispatchers.Main) {
                        wrap._finish.invoke()
                        wrap._downloadFile.invoke(100, total.toLong(), file)
                    }
                }

                if (responseCode >= 300 || responseCode != HttpURLConnection.HTTP_OK) {
                    val tips =
                        "HTTP Request is not success, Response code is $responseCode, msg is $responseMsg"
                    netLog("downloadFile-tips", tips)
                    wrap.callFailed(msg = responseMsg)
                }

                inputStream.close()
                connection.disconnect()
            } catch (e: Exception) {
                e.printStackTrace()
                wrap.callFailed()
            }
        }
    }

}